Packer3DStackNoRotate = function(length, width, height) {
    this.init(length, width, height);
};

Packer3DStackNoRotate.prototype = {
    init: function(length, width, height) {
        this.root = {
            length: length,
            width: width,
            height: height,
            x: 0,
            y: 0,
            z: 0,
            used: false,
            volume: length * width * height
        };
    },

    signUsed: function(node, block) {
        block.used = true;
        block.x = node.x;
        block.y = node.y;
        block.z = node.z;

        node.used = true;
        node.top = {
            length: block.length,
            width: block.width,
            height: node.height - block.height,
            x: node.x,
            y: node.y,
            z: node.z + block.height,
            used: false,
            volume: block.length * block.width * (node.height - block.height)
        };
        node.right = {
            length: block.length,
            width: node.width - block.width,
            height: node.height,
            x: node.x + block.width,
            y: node.y,
            z: node.z,
            used: false,
            volume: block.length * (node.width - block.width) * node.height
        };
        node.left = {
            length: node.length - block.length,
            width: node.width,
            height: node.height,
            x: node.x,
            y: node.y + block.length,
            z: node.z,
            used: false,
            volume: (node.length - block.length) * node.width * node.height
        };
        return node;
    },

    fill: function(root, block) {
        if (root.used) {
            return this.fill_children(root, block);
        } else if ((block.length <= root.length) && (block.width <= root.width) && (block.height <= root.height)) {
            return this.signUsed(root, block);
        } else {
            return null;
        }
    },

    compare_child: function(value1, value2) {
        if (value1.volume < value2.volume) {
            return -1;
        } else if (value1.volume > value2.volume) {
            return 1;
        } else {
            return 0;
        }
    },

    fill_children: function(root, block) {
        if (!root.sorted_children) {
            root.sorted_children = [root.top, root.right, root.left].sort(this.compare_child);
        }

        for(let i = 0; i < root.sorted_children.length; i++) {
            let child = root.sorted_children[i];
            result = this.fill(child, block);
            if (result)
                break;
        }
        return result;
    },

    compare_block: function(value1, value2) {
        if (value1.length > value2.length) {
            return -1;
        } else if (value1.length < value2.length) {
            return 1;
        } else if (value1.width > value2.width) {
            return -1;
        } else if (value1.width < value2.width) {
            return 1;
        } else if (value1.height > value2.height) {
            return -1;
        } else if (value1.height < value2.height) {
            return 1;
        } else {
            return 0;
        }
    },

    packer: function(blocks) {
        var n, node, block;
        blocks.sort(this.compare_block);

        for (n = 0; n < blocks.length; n++) {
            block = blocks[n];
            if (null == this.fill(this.root, block)) {
                return null;
            }
        }
        return this.report(blocks);
    },

    report: function(blocks) {
        var fit = 0, used = [], block, n, len = blocks.length;
        for (n = 0 ; n < len ; n++) {
            block = blocks[n];
            if (block.fit){
                fit = fit + (block.w * block.h);
            }
        }
        return {
            utilization: (100 * fit / (this.root.w * this.root.h)).toFixed(2) + '%',
            data: used
        }
    }
}
